package mya_dc.compilation_server;
import java.io.*;
import java.net.*;
import java.util.ArrayList;

import mya_dc.compilation_server.threads.*;
import mya_dc.compilation_server.users.*;
import mya_dc.compilation_server.database.DatabaseManager;
import mya_dc.compilation_server.gui.*;
import mya_dc.shared_classes.*;
import mya_dc.shared_classes.Message.EMessageType;
import mya_dc.shared_classes.SharedDefinitions.*;

/**
 * A class that implements the core functionality of the Compilation Server entity
 * 
 * @author      Marina Skarbovsky
 * <br>			MYA
 */
public class CompilationServer
{
	public enum EServerError
	{
		ERROR_UNABLE_TO_CREATE_SOCKETS,
		ERROR_UNKNOWN_ERROR,
		ERROR_CONNECTION_FAILED,
		NO_ERROR;
	}
	
	/**
	 * Constructor
	 *
	 * @param sWorkingDirectory - Root directory of all the compilation server's files.
	 */
	public CompilationServer(String sWorkingDirectory)
	{
		m_sDefaultWorkingDir = sWorkingDirectory;
		m_sProjectsFolder = m_sDefaultWorkingDir + "projects\\";
		m_UserProjectManager = new UserProjectManager();
		
	}
	
	/**
	 * Sets the server's connection parameters as defined by the administrator.
	 *
	 * @param nUserPort 		- The port that will be used to receive client connections.
	 * @param nMainServerPort	- Main Server's port for Compilation Servers' connections.
	 * @param MainServerAddress - Main Server's address (IP number or host name).
	 */
	public void setConnectionParamets(int nUserPort, int nMainServerPort,InetAddress MainServerAddress)
	{
		m_nUsersPort = nUserPort;
		m_nMainServerPort = nMainServerPort;
		m_MainServerAddr = MainServerAddress;
	}
	
	/**
	 * Creates the ServerSocket for client connections, and the socket for communication with the main server.
	 *
	 */
	private boolean createSockets()
	{
		try
		{
			m_UserConnectionSocket = new ServerSocket(m_nUsersPort);
			m_MainServerSocket = new Socket(m_MainServerAddr,m_nMainServerPort);
			return true;
		}
    	catch (IOException e) 
        {
    		try
    		{
    			m_UserConnectionSocket.close();
    			m_MainServerSocket.close();
    		}
    		catch (Exception e1) {}
    	    return false;
        }
	}
	
	
	/**
	 * Retrieves a reference to the Compilation Server's UserProjectManager.
	 * 
	 *  @return Reference to UserProjectManager
	 */
	public UserProjectManager getUserProjectManager()
	{
		return m_UserProjectManager;
	}
	
	
	/**
	 * Notifies the main server regarding project deletion.
	 * 
	 */
	public void sendProjectDeletedNotifacation(UserProject DeletedProject)
	{
		try
		{
			m_MainServerTransmitter.send(new Message(EMessageType.SERVER_SYNCHRONIZE_DELETE_USERPROJECT,DeletedProject));
		}
		catch(IOException e) {}
	}
	
	/**
	 * Creates a new thread to handle new client connection.
	 * 
	 * @param UserID - New user authentication details.
	 * @param CurrTransmitter - Transmitter for communication with the user.
	 * 
	 */
	private void handleNewConnection(MYA_Authentication UserID, Transmitter CurrTransmitter,boolean bNewProject)
	{
		UserConnectionThread CurrUserConnectionThread = new UserConnectionThread(UserID,bNewProject,CurrTransmitter,this,m_GUI);
		new Thread(CurrUserConnectionThread).start();
	}
	
	
	/**
	 * Creates the MainServerConnectionThread
	 * 
	 */
	public MainServerConnectionThread createMainServerConnectionThread(CompilationServerGUI GUI)
	{
		MainServerConnectionThread MainServerThread = new MainServerConnectionThread(GUI);
		new Thread(MainServerThread).start();
		return MainServerThread;
	}
	
	
	
	/**
	 * Performs registration of the compilation server in the main server.
	 * 
	 * @throws IOException - Connection to the main server was terminated.
	 */
	private void registerServerInMainServer() throws IOException
	{
		try
		{
	    	Message MainServReply = null;
	    	m_MainServerTransmitter.send(new Message(EMessageType.LOGIN_ENTITY_IDENTIFICATION,Entity.ENTITY_COMPILATION));
	    	File IDFile = new File(m_sDefaultWorkingDir + m_sIdentificationFileName);
	    	FileInputStream InputStream = null; 
	    	FileOutputStream OutputStream = null;
	    	while (true)
	    	{
	    		try
	    		{
	    			InputStream = new FileInputStream(IDFile);
	    		}
	    		catch(FileNotFoundException e)
	    		{
	    			Message NewLoginMsg = new Message(EMessageType.LOGIN_NEW,null);
	    			m_MainServerTransmitter.send(NewLoginMsg);
	    			MainServReply = m_MainServerTransmitter.receive();
	    			OutputStream = new FileOutputStream(IDFile);
	    			final Long ID = (Long)MainServReply.getObject();
	    			OutputStream.write(ID.toString().getBytes());
	    			InputStream = new FileInputStream(IDFile);
	    		}
	    		byte[] ID = new byte[(int)IDFile.length()];
	    		InputStream.read(ID);
	    		String sTemp = new String(ID);
	    		m_MainServerID = Long.parseLong(sTemp);
	    		Object[] MessageObj = {m_MainServerID, m_nUsersPort};
	    		Message ExistingLoginMsg = new Message(EMessageType.LOGIN_EXISTING,MessageObj);
	    		m_MainServerTransmitter.send(ExistingLoginMsg);
	    		MainServReply = m_MainServerTransmitter.receive();    
	    		if (MainServReply.getType() == EMessageType.LOGIN_FAILED_ID_ALREADY_IN_USE)
	    		{
	    			if (InputStream != null)
	    			{
	    				InputStream.close();
	    			}
	    			if (OutputStream != null)
	    			{
	    				OutputStream.close();
	    			}
	    			IDFile.delete();
	    			continue;
	    		}
	    		break;
	    	}
		}
		catch (ClassNotFoundException e) {}
	}
	
	
	/**
	 * Performs database synchronization with the database of the main server.
	 * 
	 * @throws IOException - Connection to the main server was terminated.
	 */
	@SuppressWarnings("unchecked")
	private void synchronizeDatabaseWithMainServer() throws IOException
	{
		try
		{
			ArrayList<UserProject> AllProjectsList = DatabaseManager.getUserProjectArray();
			if (AllProjectsList == null)
			{
				return;
			}
			m_MainServerTransmitter.send(new Message(EMessageType.SERVER_SYNCHRONIZE_DATABASE,AllProjectsList));
			Message MainServReply = m_MainServerTransmitter.receive();
			switch (MainServReply.getType())
			{
				case SERVER_SYNCHRONIZE_DELETE_USERS:
				{
					ArrayList<UserProject> UserProjectList = (ArrayList<UserProject>)MainServReply.getObject();
					//DatabaseManager.removeUserProjectsInList(UserProjectList);
					UserProjectManager.removeUsersProjects(UserProjectList, m_sDefaultWorkingDir);
					m_GUI.updateUserProjectInformationTable();
					m_GUI.updateCompilationsHistoryTable();
				}
				case SERVER_SYNCHRONIZE_OK:
				{
					return;
				}
				default:
				{
					return;
				}
			}
		} 
		catch (ClassNotFoundException e){}
	}
	
	
	/**
	 * Retrieves the Compilations Server's root directory.
	 * 
	 */
	public String getServerRoot()
	{
		return m_sDefaultWorkingDir;
	}
	
	
	/**
	 * Retrieves the server's ID at Master Server.
	 * 
	 */
	public long getServerID()
	{
		return m_MainServerID;
	}
	
	/**
	 * Retrieves available slaves from the master server
	 *
	 * @return An array of objects, containing ConnectionInfo to the slaves
	 * @throws IOException 
	 */
	public synchronized Object[] getSlaveList() 
	{
		try
		{
			Message RequestSlavesMsg = new Message(EMessageType.COMPILATION_GET_SLAVES,null);
			m_MainServerTransmitter.send(RequestSlavesMsg);
			Message ServerResponse = m_MainServerTransmitter.receive();
			if (ServerResponse.getType() != EMessageType.SUCCESS)
			{
				return null;
			}
			return (Object[])ServerResponse.getObject();
		}
		catch (Exception e)
		{
			return null;
		}
	}
	
	
	/**
	 * Retrieves the Compilations Server's root directory.
	 * 
	 */
	public void closeServer()
	{
		m_bRunServer = false;
		//MainServerConnectionThread.setRunChecks(false);
		try
		{
			m_UserConnectionSocket.close();
		}
		catch(Exception e){}
	}
	
	
	public synchronized Transmitter getMainServerTransmitter() 
	{
		return m_MainServerTransmitter;
	}
	
	
	/**
	 * Runs the Compilation Server
	 * 
	 * @param GUI - GUI renderer of the master server.
	 * 
	 */
	public EServerError begin(CompilationServerGUI GUI)
	{
        m_bRunServer = true;
        boolean bNewProject = true;
        m_GUI = GUI;
		Transmitter CurrUserTransmitter = null;
		Message CurrMessage = null;
		Socket UserSocket = null;
		if (!createSockets())
		{
			return EServerError.ERROR_UNABLE_TO_CREATE_SOCKETS;
		}
		try
		{
			m_MainServerTransmitter = new Transmitter(m_MainServerSocket,m_sDefaultWorkingDir,SharedDefinitions.SecsBetweenTwoKeepAliveMessages);
			try
			{
				registerServerInMainServer();
				synchronizeDatabaseWithMainServer();
				m_GUI.setReconnecting(false);
				m_GUI.setConnected(true);
			}
			catch(IOException e)
			{
				return EServerError.ERROR_CONNECTION_FAILED;
			}		
			while (m_bRunServer) 
		    {
				try
				{
		        	UserSocket = m_UserConnectionSocket.accept();
		        	CurrUserTransmitter = new Transmitter(UserSocket,m_sProjectsFolder,SharedDefinitions.SecsBetweenTwoKeepAliveMessages);
		        	if (!SharedDefinitions.isExpectedEntity(CurrUserTransmitter,Entity.ENTITY_CLIENT,true))
		        	{
		        		continue;
		        	}
		        	CurrMessage = CurrUserTransmitter.receive();
		        	switch (CurrMessage.getType())
		        	{
		        		case LOGIN:
		        		{
		        			MYA_Authentication CurrID = (MYA_Authentication)CurrMessage.getObject(); 
		        			if (m_UserProjectManager.isUserConnected(CurrID.getUserName(), CurrID.getProject()))
		        			{
		        				CurrUserTransmitter.send(new Message(EMessageType.LOGIN_EXISTING,null));
		        				CurrUserTransmitter.close();
		        				break;
		        			}
		        			if (DatabaseManager.checkProjectRegistrated(CurrID.getUserName(),CurrID.getProject()))
		    				{
		        				bNewProject = false;
		        				CurrUserTransmitter.send(new Message(EMessageType.SUCCESS,null));
		    				}
		        			else
		        			{
		        				if (!UserProjectManager.addNewUserProject(CurrID,CurrUserTransmitter))
		        				{
		    						break;
		        				}
		        				bNewProject = true;
		        				m_GUI.updateUserProjectInformationTable();
		        			}
		        			handleNewConnection(CurrID,CurrUserTransmitter,bNewProject);
		        			break;
		        		}
		        		default:
		        		{
		        			CurrMessage = new Message(EMessageType.UNEXPECTED_MESSAGE,"User Authentication expected.\n");
		        			CurrUserTransmitter.send(CurrMessage);
		        			CurrUserTransmitter.close();
		        			continue;
		        		}
		        	}
		        	UserSocket = null;		        	
				}
				catch(IOException e)
				{
					if (UserSocket != null)
					{
						CurrUserTransmitter.close();
					}
					m_bRunServer = false;
				}
		    }
		}
		catch(Exception e)
		{
			return EServerError.ERROR_UNKNOWN_ERROR;
		}
		finally
		{
			try
			{
				m_MainServerTransmitter.close();
				m_UserConnectionSocket.close();
				m_GUI.setConnected(false);
			}
			catch(Exception e) {}
	    }
		return EServerError.NO_ERROR;
    } 
	
	
	/**
	 * Sets the minimum allowed free space on the disk. If the limit is reached, new users won't be registrated
	 * until space is freed.
	 *
	 * @param	NewMinimum - the amount of bytes that must be left unused on the disk.
	 */
	public static synchronized void setFreeSpaceMinimum(Long NewMinimum)
	{
		if (NewMinimum < 0)
			return;
		m_FreeSpaceMinimum = NewMinimum;
	}
	
	/**
	 * Returns the current free space minimum
	 *
	 */
	public static synchronized long getFreeSpaceMinimum()
	{
		return m_FreeSpaceMinimum;
		
	}
	
	//Member variables
	private CompilationServerGUI m_GUI = null;
	private boolean m_bRunServer;
	private int m_nMainServerPort;
	private int m_nUsersPort;
	private ServerSocket m_UserConnectionSocket;
	private Socket m_MainServerSocket;
	private InetAddress  m_MainServerAddr;
	private UserProjectManager m_UserProjectManager;
	private Transmitter m_MainServerTransmitter;
    private String m_sDefaultWorkingDir;
    private String m_sProjectsFolder;
    private Long m_MainServerID = new Long("-1");
	final static String m_sIdentificationFileName = "ServerID.txt";
	final static int m_sErrorBound = 3;
	private static long m_FreeSpaceMinimum = 10485760; // 10 MB
	

	
}
